﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LowLevelGraphics.Filter
{
    public sealed class Reinhard05 : Operator
    {
        public double Intensity
        {

            get
            {
                return this.f;
            }
            set
            {
                this.f = Math.Min(Math.Max(value, -8), 8);
            }
        }

        public double Contrast
        {

            get
            {
                return this.m;
            }
            set
            {
                this.m = Math.Min(Math.Max(value, 0), 1);
            }
        }

        public double Adaption
        {

            get
            {
                return this.a;
            }
            set
            {
                this.a = Math.Min(Math.Max(value, 0), 1);
            }
        }

        public double ColorCorrection
        {

            get
            {
                return this.c;
            }
            set
            {
                this.c = Math.Min(Math.Max(value, 0), 1);
            }
        }

        private double f = 0;
        private double m = 0;
        private double a = 1;
        private double c = 0;

        public Reinhard05(double intensity, double contrast, double adaption, double colorCorrection)
        {

            this.Intensity = intensity;
            this.Contrast = contrast;
            this.Adaption = adaption;
            this.ColorCorrection = colorCorrection;
        }

        public Reinhard05()
        {
        }

        protected override void Run(float[][,] mapInput, int width, int height, ref float[][,] mapOutput)
        {

            float[] Cav = new float[3]; // channel average
            float Lav = 0; // average luminance
            float Llav = 0; // log average luminance
            float maxLum = 0, minLum = 0; // min, max luminance
            float L = 0; // pixel luminance
            float Ia = 0; // pixel adaption
            float Ig = 0, Il = 0; // global, local
            float k = 0; // key
            float f = 0, m = 0;

            int i = 0, x = 0, y = 0;
            float color = 0;

            float[,] Y = null;

            float maxColor = Single.MinValue;
            float minColor = Single.MaxValue;

            GetLuminance(mapInput, ref Y, ref maxLum, ref minLum, ref Lav);

            Llav = (float)Math.Log(Lav);
            f = (float)Math.Exp(this.f);
            k = (float)((Math.Log(maxLum) - Llav) / (Math.Log(maxLum) - Math.Log(minLum)));
            m = (float)((this.m > 0) ? this.m : (float)(0.3 + 0.7 * Math.Pow(k, 1.4)));

            if (this.a == 1 && this.c == 0)
            {

                #region fast tone mapping

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        Ia = Y[x, y];

                        for (i = 0; i < 3; i++)
                        {

                            color = mapInput[i][x, y];

                            if (color != 0)
                                mapInput[i][x, y] = (float)(color / (color + Math.Pow(f * Ia, m)));

                            if (color > maxColor)
                                maxColor = color;

                            if (color < minColor)
                                minColor = color;
                        }
                    }

                #endregion
            }
            else
            {

                #region normal tone mapping

                Cav[0] = Cav[1] = Cav[2] = 0;

                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        for (i = 0; i < 3; i++)
                            Cav[i] += mapInput[i][x, y];
                    }

                float imageSize = (width * height);

                for (i = 0; i < 3; i++)
                    Cav[i] /= imageSize;

                // perform tone mapping
                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                    {

                        L = Y[x, y];

                        for (i = 0; i < 3; i++)
                        {

                            color = mapInput[i][x, y];

                            if (color != 0)
                            {

                                Il = (float)(c * color + (1 - c) * L);
                                Ig = (float)(c * Cav[i] + (1 - c) * Lav);
                                Ia = (float)(a * Il + (1 - a) * Ig);

                                mapInput[i][x, y] = (float)(color / (color + Math.Pow(f * Ia, m)));
                            }

                            if (color > maxColor)
                                maxColor = color;

                            if (color < minColor)
                                minColor = color;
                        }
                    }

                #endregion
            }

            // normalize to output
            for (i = 0; i < 3; i++)
                for (y = 0; y < height; y++)
                    for (x = 0; x < width; x++)
                        mapOutput[i][x, y] = Math.Min(Math.Max((mapInput[i][x, y] - minColor) / (maxColor - minColor), 0), 1);
        }

        protected override void SetParam(string param, string value)
        {

            switch (param)
            {
                case "intensity":
                    this.Intensity = Double.Parse(value);
                    break;
                case "contrast":
                    this.Contrast = Double.Parse(value);
                    break;
                case "adaption":
                    this.Adaption = Double.Parse(value);
                    break;
                case "colorcorrection":
                    this.ColorCorrection = Double.Parse(value);
                    break;
            }
        }

        private void GetLuminance(float[][,] map, ref float[,] Y, ref float maxLum, ref float minLum, ref float worldLum)
        {

            int width = map[0].GetLength(0);
            int height = map[0].GetLength(1);

            int x = 0, y = 0;
            float lum = 0;

            Y = new float[width, height];
            maxLum = Single.MinValue;
            minLum = Single.MaxValue;
            worldLum = 0;

            for (y = 0; y < height; y++)
                for (x = 0; x < width; x++)
                {

                    lum = (float)(0.2126 * map[2][x, y] + 0.7512 * map[1][x, y] + 0.0722 * map[0][x, y]);

                    Y[x, y] = lum;

                    if (lum > maxLum)
                        maxLum = lum;

                    if (lum < minLum)
                        minLum = lum;

                    worldLum += lum;
                }

            worldLum /= (width * height);
        }
    }
}
